//
//  DoorLockTool.swift
//  ZhiTing
//
//  Created by iMac on 2022/4/27.
//

import Foundation
import CryptoSwift
import CoreBluetooth
import Combine


// MARK: - DoorLock
class DoorLock: BaseModel {
    var area_id: String?
    /// 设备名称
    var name = ""
    /// 设备型号
    var model = ""
    /// 设备唯一标识
    var uuid = ""
    /// 用于数据加密的设备PIN码
    var pin: Data?
    /// 通信数据是否需要PIN码加解密
    var needEncode = true
    /// 蓝牙mac地址
    var mac_addr: Data?
    /// 配对码
    var pair = ""
    /// 插件包id
    var plugin_id: String?

    var peripheral: CBPeripheral?
    
    /// 设备id(未注册时是蓝牙mac地址后四位)
    var device_id: [UInt8]? {
        guard let mac_addr = mac_addr else {
            return nil
        }
        return Array(mac_addr).suffix(4)
    }
    
    /// 转换成首页设备model
    func toDevice() -> Device {
        let d = Device()
        d.name = name
        d.iid = uuid
        d.model = model
        d.is_private = true
        d.is_online = true
        d.plugin_id = plugin_id ?? ""
        d.logo_image = .assets(.smart_lock)
        d.control = "html/index.html?iid=\(uuid)&model=\(model)&name=\(name)&plugin_id=\(plugin_id ?? "")&type=door_lock&is_ble=1"
        return d
    }
    
    func toSupportDeviceJson() -> String {
        return "{\"uuid\": \"\(uuid)\", \"name\": \"\(name)\", \"model\": \"\(model)\", \"mac\": \"\(mac_addr?.toHexString() ?? "")\"}"
    }

}


// MARK: - DoorLockUtil
final class DoorLockUtil: NSObject {
    let centralManager: CBCentralManager
    
    var peripheral: CBPeripheral?
    
    var writeCharacteristc: CBCharacteristic?
    
    /// 当前门锁设备
    var doorLock: DoorLock?
    
    /// 扫描列表
    var scanList = [DoorLock]()
    /// 扫描名字过滤
    var scanFilter: String = "MH-"
    
    /// 执行发送帧数据后的响应 (命令类型, 响应json字符串)
    var operationFrameResponse = PassthroughSubject<(UInt8, String), Never>()
    /// 上一次执行的命令
    var lastOperation: OperationFrame?
    
    /// 设备连接回调
    var connectCallback: ((String) -> ())?
    /// 设备设置PIN回调
    var pinCallback: ((String) -> ())?
    /// 设备连接状态回调 0连上 1断开
    var connectstateCallback: ((Int) -> ())?
    /// 门锁日志回调
    var logCallback: ((String) -> ())?

    deinit {
        print("DoorLockUtil deinit.")
    }

    override init() {
        centralManager = CBCentralManager()
        super.init()
        centralManager.delegate = self
        /// 解决息屏时无法接受消息的问题 息屏时先断开连接 亮屏再重连
        NotificationCenter
            .default
            .addObserver(forName: UIApplication.willResignActiveNotification,
                         object: nil,
                         queue: OperationQueue.main) { [weak self] _ in
                guard let self = self else { return }
                if let p = self.peripheral,
                   p.state == .connected,
                   !self.scanList.contains(where: {$0.uuid == p.identifier.uuidString }) {
                    self.centralManager.cancelPeripheralConnection(p)
                }
            }

        NotificationCenter
            .default
            .addObserver(forName: UIApplication.didBecomeActiveNotification,
                         object: nil,
                         queue: OperationQueue.main) { [weak self] _ in
                guard let self = self else { return }
                if let p = self.peripheral,
                   p.state == .disconnected,
                   !self.scanList.contains(where: {$0.uuid == p.identifier.uuidString }) {
                    self.centralManager.connect(p)
                }
            }

        
    }
    
    func retrieve(by doorLock: DoorLock) {
        if let identifier = UUID(uuidString: doorLock.uuid),
           let p = centralManager.retrievePeripherals(withIdentifiers: [identifier]).first {
            doorLock.peripheral = p
            self.doorLock = doorLock
            peripheral = p
            centralManager.connect(p)
        } else {
            print("retrieve doorLock failed.")
        }
    }
    
    /// 扫描设备
    func scan(seconds: Int, scanFilter: String = "MH-", callback: (([DoorLock]) -> ())?) {
        self.scanFilter = scanFilter
        scanList.removeAll()
        centralManager.scanForPeripherals(withServices: nil, options: nil)
        DispatchQueue.main.asyncAfter(deadline: .now() + TimeInterval(seconds)) { [weak self] in
            guard let self = self else { return }
            self.centralManager.stopScan()
            callback?(self.scanList)
        }
    }
    
    /// 连接设备
    func connect(uuid: String, callback: ((String) -> ())?) {
        centralManager.stopScan()
        self.connectCallback = callback
        /// 从搜索列表连接
        if let doorLock = scanList.first(where: { $0.uuid == uuid }) {
            self.doorLock = doorLock
            self.peripheral = doorLock.peripheral
            if let peripheral = self.peripheral {
                self.connectCallback = callback
                centralManager.connect(peripheral)
            }
            return
        }
        
        /// 从缓存的设备连接
        if let identifier = UUID(uuidString: uuid),
           let p = centralManager.retrievePeripherals(withIdentifiers: [identifier]).first {
            self.doorLock = getDoorLock(uuid: uuid)
            self.doorLock?.peripheral = p
            peripheral = p
            centralManager.connect(p)
            return
        }
        
    }
    
    /// 设置pin码
    /// - Parameter pin: pin
    /// - Parameter plugin_id: 插件包id
    func setPin(pin: String, plugin_id: String?, callback: ((String) -> ())?) {
        let data = pin.hexaData
        pinCallback = callback
        doorLock?.plugin_id = plugin_id
        doorLock?.pin = data
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyyMMddHHmmss"
        let date = formatter.string(from: Date())

        sendFrameData(.setSystemTime(date: date))
    }

    
    /// 发送帧数据
    /// - Parameter frame: 帧数据
    func sendFrameData(_ frame: OperationFrame, callback: ((String) -> ())? = nil) {
        guard
            let doorLock = doorLock,
            let deviceId = doorLock.device_id,
            let peripheral = peripheral,
            let characteristic = writeCharacteristc,
            peripheral.state == .connected
        else {
            print("发送FrameData失败")
            callback?("{\"status\": -1, \"error\": \"操作失败\"}")
            return
        }
        print("准备写入数据:\n \(frame.frameData(device_id: deviceId).map { " 0x" + String($0, radix: 16, uppercase: true) }.joined())")
        
        var cancellable: AnyCancellable?
        cancellable = operationFrameResponse
            .receive(on: DispatchQueue.main)
            .sink { cmdType, json in
                if cmdType == frame.cmdType {
                    callback?(json)
                    cancellable?.cancel()
                }
            }
            
        lastOperation = frame
        if doorLock.needEncode { /// 数据通讯需要加密
            if let encryptedData = encrypt(data: frame.frameData(device_id: deviceId)) {
                DispatchQueue.main.asyncAfter(deadline: .now()+0.5) {
                    self.write(peripheral: peripheral, characteristic: characteristic, data: encryptedData)
                }
                
            } else {
                print("发送FrameData失败")
            }
        } else { /// 数据通讯不需要加密
            DispatchQueue.main.asyncAfter(deadline: .now()+0.5) {
                self.write(peripheral: peripheral, characteristic: characteristic, data: frame.frameData(device_id: deviceId))
            }
        }
        
    }
    
    
    
    /// 写数据
    private func write(peripheral: CBPeripheral, characteristic: CBCharacteristic, data: Data, type: CBCharacteristicWriteType = .withoutResponse) {
        let bytes = [UInt8](data)
        var send = [UInt8]()
        let mode = peripheral.maximumWriteValueLength(for: type)
        if bytes.count > mode {
            var i = 1
            let total = bytes.count / mode + 1
            while i<=total {
                if i==total{
                    if (i-1)*mode <= bytes.count-1 {
                        send = Array(bytes[(i-1)*mode...(bytes.count-1)])
                    } else {
                        break
                    }
                } else {
                    send = Array(bytes[(i-1)*mode...(mode*i-1)])
                }
                
                let data = Data(send)
                peripheral.writeValue(data, for: characteristic, type: type)
                
                //两包数据间间隔500毫秒
                Thread.sleep(forTimeInterval: 0.5)
                i += 1
            }
        } else {
            peripheral.writeValue(data, for: characteristic, type: type)
        }
    }
    
    /// 读数据
    private func read(peripheral: CBPeripheral, characteristic: CBCharacteristic) {
        peripheral.readValue(for: characteristic)
    }
    
}

// MARK: - CBCentralManagerDelegate
extension DoorLockUtil: CBCentralManagerDelegate {
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        print("centralManagerDidUpdateState: \(central.state)")
        if central.state != .poweredOn {
            connectstateCallback?(1)
        }
    }
    
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
        if peripheral.name != nil {
            print(peripheral)
        }
        /// 广播包
        /// | 厂商编号 (2byte) | 配对码标识(1byte) | 加密标识位(1byte) | 广播包标识(1byte) | 配对码数据长度(1byte) | 配对码(6byte) | mac地址数据长度（1byte） | 蓝牙mac地址（6byte） |
        /// 厂商编号：固定0x6050，我们定义的厂商编号。
        /// 配对码标识：0x00广播的普通数据包；0x55广播的带配对码的数据包。
        /// 加密标识位：0x00通讯不需要加密；0x01通讯需要pin码加密数据。
        /// 广播包标识位: 0x00普通数据广播包 0x55带配对码广播包
        /// 配对码数据长度：配对码的长度。
        /// 配对码：目前配对码的长度为6个字节，设备连接的蓝牙配对码为6位数，所以每个字节对应配对码的一位数。
        /// 蓝牙mac地址的长度。
        /// 蓝牙设备的mac地址，6个字节。
        
        if let name = peripheral.name, name.hasPrefix(scanFilter) {
            guard
                let data = advertisementData["kCBAdvDataManufacturerData"] as? Data,
                data.count > 6,
                data[0] == 0x60 && data[1] == 0x50 && data[4] == 0x55
            else {
                return
            }
            
            let str = data
                .map { " 0x" + String($0, radix: 16, uppercase: true) }
                .joined()
            print(str)
            
            let doorLock = DoorLock()
            doorLock.uuid = peripheral.identifier.uuidString
            doorLock.name = peripheral.name ?? ""
            doorLock.model = peripheral.name ?? ""
            doorLock.pin = Data([0x12, 0x34, 0x56, 0x78])
            doorLock.needEncode = data[3] == 0x01
            doorLock.peripheral = peripheral
            doorLock.area_id = AuthManager.shared.currentArea.id
            /// 设备PIN码
            let pairLen = Int(data[5])
            let pair = data.subdata(in: 6..<6+pairLen)
            let pairStr = pair.map{ String($0, radix: 16, uppercase: true) }.joined()
            print("蓝牙设备配对码:\(pairStr)")
            doorLock.pair = pairStr
            /// 设备MAC地址
            if data.count >= 6 + pairLen {
                let macLen = Int(data[6+pairLen])
                if data.count >= 6 + pairLen + macLen {
                    let deviceId = data.subdata(in: 6+pairLen+1..<6+pairLen+1+macLen)
                    doorLock.mac_addr = deviceId
                    print("蓝牙设备MAC:\(deviceId.map{ " 0x" + String($0, radix: 16, uppercase: true) }.joined())")
                }
                

            }
            
            if !scanList.contains(where: { $0.uuid == peripheral.identifier.uuidString })  {
                scanList.append(doorLock)
            }
            
        }
    }
    
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        print("connected.")
        peripheral.delegate = self
        peripheral.discoverServices(nil)
    }
    
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        print("fail to connected. error:\(String(describing: error))")
        if (error as? NSError)?.code == 14 {
            connectCallback?("{\"status\": -2, \"error\": \"请先到设置解除蓝牙配对\"}")
            connectCallback = nil
        }
        
    }
    
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        print("disconnected. error:\(String(describing: error))")
        connectstateCallback?(1)
        connectCallback?("{\"status\": -1, \"error\": \"配对码错误\"}")
        connectCallback = nil
    }
}

// MARK: - CBPeripheralDelegate
extension DoorLockUtil: CBPeripheralDelegate {
    func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        guard let services = peripheral.services else { return }
        /// 搜索外设的特征
        for service in services {
            peripheral.discoverCharacteristics(nil, for: service)
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        if error == nil {
            service.characteristics?.forEach {
                if $0.properties.contains(.notify)  {
                    peripheral.setNotifyValue(true, for: $0)
                }

                if $0.properties.contains(.writeWithoutResponse) && $0.uuid == CBUUID.init(string: "FFC1") {
                    self.writeCharacteristc = $0
                }
            }
        } else {
            print("发现设备特征错误: \(String(describing: error))")
        }
        
        if peripheral.services?.last == service {
            print("发现了外设的所有特征，准备就绪")
            peripheral.services?.forEach { service in
                
                print("-----service-------")
                print(service)
                print("------------------")
                service.characteristics?.forEach { c in
                    print(c)
                }
                print("------------------")
            }

        }

    }
    
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) {
        print(descriptor)
    }
    
    func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        if error == nil, let data = characteristic.value {
            print("特征接受到信息(\(characteristic)):\n \(data.map { " 0x" + String($0, radix: 16, uppercase: true) }.joined())")
            handleReceivedData(data: data)
        } else {
            print("特征接受信息错误(\(characteristic)): \(String(describing: error))")
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
        if error == nil {
            print("特征订阅成功(\(characteristic.uuid.uuidString))")
            if let d = self.doorLock {
                cacheDoorLock(doorLock: d)
            }
            
            if doorLock?.pin != nil && characteristic.uuid == CBUUID(string: "FFC1") { /// 获取日志 & 获取验证方式
                let formatter = DateFormatter()
                formatter.dateFormat = "yyyyMMddHHmmss"
                let date = formatter.string(from: Date())
                sendFrameData(.setSystemTime(date: date))
                sendFrameData(.queryRegisteredUser(type: .all))
                sendFrameData(.queryLog)
            }
            connectstateCallback?(0)
            connectCallback?("{\"status\": 0, \"error\": \"\"}")
            connectCallback = nil
        } else {
            if let error = error {
                print("特征订阅失败(\(characteristic.uuid.uuidString))  error:\(String(describing: error))")
                if (error as NSError).code == 5 {
                    centralManager.connect(peripheral)
                }
            }
        }
    }
    
    func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        if error == nil {
            print("写入特征成功(\(characteristic.uuid.uuidString)")
        } else {
            print("写入特征失败(\(characteristic.uuid.uuidString))  error:\(String(describing: error))")
        }
    }
    
    func peripheralIsReady(toSendWriteWithoutResponse peripheral: CBPeripheral) {
        print("写入特征(\(String(describing: writeCharacteristc)))")

    }
    
    
    
}

